package br.atech.research;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Locale;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

import br.jm.i18n.I18NGeneratorTypeSafe;
import br.jm.i18n.MessageProvider;
import br.jm.i18n.ResourceBundleMessageProvider;

@Mojo(name = "genContainers", defaultPhase = LifecyclePhase.PROCESS_CLASSES)
public class CreateI18NResourcesMojo extends AbstractMojo {

	class DelVisitor implements FileVisitor<Path> {

		@Override
		public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
			return FileVisitResult.CONTINUE;
		}

		@Override
		public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
			return FileVisitResult.CONTINUE;
		}

		@Override
		public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
			if (file.getFileName().toString().endsWith(".properties")) {
				Files.delete(file);
			}

			return FileVisitResult.CONTINUE;
		}

		@Override
		public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
			throw exc;
		}

	}

	class GenVisitor implements FileVisitor<Path> {

		@Override
		public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
			return FileVisitResult.CONTINUE;
		}

		@Override
		public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
			return FileVisitResult.CONTINUE;
		}

		@Override
		public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
			if (file.getFileName().toString().endsWith(".class")) {
				getLog().info("Processing " + file.getFileName());

				for (final Locale l : locales) {
					spin(file, l);
				}

			}

			return FileVisitResult.CONTINUE;
		}

		@Override
		public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
			throw exc;
		}

	}

	@Parameter
	private String[] supportedLocales;

	private Locale[] locales;

	@Component
	private MavenProject project;

	MessageProvider globalProvider;

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {
		final Log log = getLog();

		if ((supportedLocales == null) || (supportedLocales.length == 0)) {
			throw new MojoExecutionException("Supported Locales parameter not defined!");
		}

		final String id = project.getArtifactId();

		final String directory = project.getBuild().getOutputDirectory();

		log.info(MessageFormat.format("Starting processing artifact {0}. Classes will be scaned on {1}", id, directory));
		log.info(MessageFormat.format("Generating messages for the following locales: {0}", Arrays.toString(supportedLocales)));

		final Locale[] locs = new Locale[supportedLocales.length];
		final Locale[] availableLocales = Locale.getAvailableLocales();

		for (int i = 0; i < locs.length; i++) {
			for (final Locale locale : availableLocales) {
				if (locale.toString().equals(supportedLocales[i])) {
					locs[i] = locale;
					break;
				}
			}

			if (locs[i] == null) {
				throw new MojoExecutionException("Unable to find locale for " + supportedLocales[i]);
			}
		}

		this.locales = locs;
		try {
			Files.walkFileTree(Paths.get(directory), new GenVisitor());

			Files.walkFileTree(Paths.get(directory), new DelVisitor());
		} catch (final IOException e) {
			throw new MojoExecutionException(e.getMessage());
		}
	}

	MessageProvider getProvider(final Path classFile, final Locale loc) throws IOException {
		return globalProvider == null ? new ResourceBundleMessageProvider(classFile, loc) : globalProvider;
	}

	public void spin(final Path path, final Locale loc) {
		final String className = path.getFileName().toString().replace(".class", "");

		if (className.indexOf('@') > 0) {
			getLog().warn("Ignoring " + className);
			return;
		}

		try (InputStream is = Files.newInputStream(path)) {
			final Path target = path.resolveSibling(MessageFormat.format("{0}@{1}.class", className, loc));

			getLog().info("Generating " + target.getFileName());

			Files.deleteIfExists(target);
			Files.createFile(target);

			final ClassReader cr = new ClassReader(is);
			final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
			final MessageProvider mp = getProvider(path, loc);
			final I18NGeneratorTypeSafe gen = new I18NGeneratorTypeSafe(cw, mp, loc);

			cr.accept(gen, ClassReader.SKIP_DEBUG);

			final byte[] bc = cw.toByteArray();

			Files.write(target, bc);
		} catch (final IOException e) {
			throw new RuntimeException(e);
		}
	}
}
